#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
优化版小米便签自动化导出工具
解决小米便签网页版导出过程中的各种问题，实现完全自动化提取
"""

import os
import sys
import json
import time
import logging
from datetime import datetime
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException, WebDriverException

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('mi_note_auto_export.log', encoding='utf-8'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

class MiNoteAutoExporter:
    def __init__(self, keep_browser_open=False):
        # 默认ChromeDriver路径（使用原始字符串避免转义问题）
        self.driver_path = r"C:\Program Files\chrome\chromedriver.exe"
        self.driver = None
        self.output_dir = "./exports"
        self.note_list_window = None  # 记录便签列表窗口
        self.keep_browser_open = keep_browser_open  # 是否保持浏览器不关闭
        self.driver_options = None  # 浏览器选项
        self.selectors_tried = set()  # 已经尝试过的选择器集合
        self.consecutive_failures = 0  # 连续失败次数
        self.max_consecutive_failures = 3  # 最大连续失败次数
        self.session_id = None  # WebDriver会话ID
        self.note_list_url = "https://i.mi.com/note"  # 便签列表URL
        
        # 添加重试配置
        self.max_retries = 3
        self.retry_interval = 5
        
        # 优化：增加单个便签处理的超时时间
        self.single_note_timeout = 60  # 单个便签处理最长60秒
        self.current_note_index = 0  # 当前处理的便签索引
    
    def check_environment(self):
        """检查运行环境是否满足要求"""
        logger.info("开始检查运行环境...")
        
        # 检查ChromeDriver是否存在
        if not os.path.exists(self.driver_path):
            logger.error(f"ChromeDriver文件不存在: {self.driver_path}")
            print(f"❌ ChromeDriver文件不存在: {self.driver_path}")
            print("请修改脚本中的driver_path变量为正确的ChromeDriver路径")
            return False
        
        # 创建输出目录
        if not os.path.exists(self.output_dir):
            try:
                os.makedirs(self.output_dir)
                logger.info(f"创建输出目录成功: {self.output_dir}")
            except Exception as e:
                logger.error(f"创建输出目录失败: {str(e)}")
                print(f"❌ 创建输出目录失败: {str(e)}")
                return False
        
        logger.info("环境检查通过")
        return True
    
    def init_browser(self):
        """初始化浏览器"""
        logger.info("开始初始化浏览器...")
        
        try:
            # 创建Chrome选项
            self.driver_options = Options()
            self.driver_options.add_argument("--start-maximized")  # 最大化窗口
            self.driver_options.add_argument("--disable-gpu")  # 禁用GPU加速
            self.driver_options.add_argument("--no-sandbox")  # 禁用沙盒
            self.driver_options.add_argument("--disable-extensions")  # 禁用扩展
            self.driver_options.add_argument("--disable-popup-blocking")  # 禁用弹窗拦截
            self.driver_options.add_argument("--remote-debugging-port=9222")  # 启用调试端口
            self.driver_options.add_argument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36")  # 设置UA
            
            # 禁用自动化控制特征
            self.driver_options.add_experimental_option("excludeSwitches", ["enable-automation"])
            self.driver_options.add_experimental_option("useAutomationExtension", False)
            
            # 创建WebDriver服务
            service = Service(executable_path=self.driver_path)
            
            # 启动Chrome浏览器
            self.driver = webdriver.Chrome(service=service, options=self.driver_options)
            
            # 隐藏自动化控制提示
            self.driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
                "source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
            })
            
            # 记录会话信息，但不使用_url属性
            self.session_id = self.driver.session_id
            
            logger.info("Chrome浏览器启动成功")
            return True
        except WebDriverException as e:
            logger.error(f"启动Chrome浏览器失败: {str(e)}")
            print(f"❌ 启动Chrome浏览器失败: {str(e)}")
            print("请检查ChromeDriver路径是否正确，以及ChromeDriver版本是否与Chrome浏览器版本匹配")
            return False
        except Exception as e:
            logger.error(f"浏览器初始化过程中发生未预期错误: {str(e)}")
            print(f"❌ 浏览器初始化过程中发生未预期错误: {str(e)}")
            return False
    
    def navigate_to_notes(self):
        """导航到小米便签页面"""
        logger.info("正在导航到小米便签页面...")
        
        try:
            # 导航到小米云服务便签页面
            self.driver.get(self.note_list_url)
            
            # 等待页面加载
            wait = WebDriverWait(self.driver, 30)
            wait.until(lambda d: d.execute_script('return document.readyState') == 'complete')
            
            # 强制显示登录提示，让用户手动登录
            logger.info("提示用户手动登录")
            print("\n请在打开的浏览器中完成以下操作：")
            print("1. 如果需要，登录您的小米云服务账号")
            print("2. 确保能看到您的便签列表")
            print("3. 然后返回此窗口按Enter键继续...")
            input()
            
            # 刷新页面确保便签列表已加载
            self.driver.refresh()
            time.sleep(3)
            
            # 记录便签列表页面的URL，用于后续返回
            self.note_list_window = self.driver.current_window_handle
            
            return True
        except Exception as e:
            logger.error(f"导航到便签页面失败: {str(e)}")
            print(f"❌ 导航到便签页面失败: {str(e)}")
            return False
    
    def get_all_notes(self):
        """获取所有便签内容（包括滚动加载更多）"""
        logger.info("开始获取便签列表...")
        
        notes_data = []
        
        try:
            # 等待便签列表加载完成
            wait = WebDriverWait(self.driver, 60)  # 延长等待时间
            
            # 使用多种选择器尝试找到便签列表元素
            note_elements = []
            
            # 简化选择器策略，避免过度过滤
            # 第一阶段：使用基础选择器
            base_selectors = [
                (By.CLASS_NAME, "note-list-item"),
                (By.CLASS_NAME, "note-item"),
                (By.XPATH, "//div[contains(@class, 'note-list-item') or contains(@class, 'note-item')]")
            ]
            
            # 尝试基础选择器
            for by, selector in base_selectors:
                try:
                    logger.info(f"尝试基础选择器: {by}={selector}")
                    temp_elements = wait.until(EC.presence_of_all_elements_located((by, selector)))
                    
                    if temp_elements and len(temp_elements) > 0:
                        # 检查元素数量和大小，判断是否可能是单个容器
                        if len(temp_elements) == 1:
                            logger.warning(f"只找到1个元素，检查是否为容器...")
                            # 对于单个元素，尝试从其内部查找子元素
                            try:
                                child_elements = temp_elements[0].find_elements(By.XPATH, "./*")
                                logger.info(f"找到{len(child_elements)}个子元素")
                                # 如果有多个子元素，可能是容器
                                if len(child_elements) > 3:
                                    logger.info(f"使用容器内的子元素作为便签项")
                                    note_elements = child_elements
                                    break
                            except:
                                pass
                        
                        # 正常情况下直接使用找到的元素
                        note_elements = temp_elements
                        logger.info(f"成功找到{len(note_elements)}条便签")
                        break
                except (TimeoutException, WebDriverException) as e:
                    logger.warning(f"基础选择器失败: {str(e)}")
                    continue
            
            # 第二阶段：如果基础选择器失败，使用备选选择器
            if not note_elements:
                logger.info("基础选择器失败，尝试备选选择器")
                alt_selectors = [
                    (By.XPATH, "//div[starts-with(@class, 'note-list-item')]"),
                    (By.XPATH, "//div[contains(@id, 'note-item')]"),
                    (By.CSS_SELECTOR, "[class*='note'][class*='list'][class*='item']")
                ]
                
                for by, selector in alt_selectors:
                    try:
                        logger.info(f"尝试备选选择器: {by}={selector}")
                        temp_elements = wait.until(EC.presence_of_all_elements_located((by, selector)))
                        if temp_elements and len(temp_elements) > 0:
                            note_elements = temp_elements
                            logger.info(f"成功找到{len(note_elements)}条便签")
                            break
                    except:
                        continue
            
            # 第三阶段：如果都失败，尝试获取所有可点击元素，但不过度过滤大小
            if not note_elements:
                logger.warning("所有选择器都失败，尝试获取页面上的所有可点击元素")
                all_elements = self.driver.find_elements(By.XPATH, "//*[not(ancestor::script) and not(ancestor::style)]")
                note_elements = [el for el in all_elements if el.is_displayed() and el.size['width'] > 50 and el.size['height'] > 20]
                logger.info(f"找到{len(note_elements)}个可能的便签元素")
            
            # 优化的滚动加载更多便签策略
                if note_elements:
                    logger.info("开始滚动加载更多便签...")
                    print("开始滚动加载更多便签...")
                     
                    # 记录初始便签数量
                    initial_count = len(note_elements)
                    max_scroll_attempts = 10  # 增加最大滚动尝试次数
                    scroll_pause_time = 3   # 增加滚动后暂停时间，确保有足够时间加载
                    scroll_attempts = 0
                    last_count = initial_count
                     
                    # 尝试多种滚动策略
                    scroll_strategies = [
                        "window.scrollTo(0, document.body.scrollHeight);",  # 滚动到页面底部
                        "window.scrollBy(0, window.innerHeight);"  # 滚动一个视口高度
                    ]
                    strategy_index = 0
                     
                    # 滚动直到无法加载更多便签或达到最大尝试次数
                    while scroll_attempts < max_scroll_attempts:
                        # 尝试不同的滚动策略
                        scroll_script = scroll_strategies[strategy_index % len(scroll_strategies)]
                        logger.info(f"使用滚动策略 {strategy_index % len(scroll_strategies) + 1}: {scroll_script}")
                        self.driver.execute_script(scroll_script)
                        time.sleep(scroll_pause_time)
                         
                        # 尝试模拟用户滚动动作，这可能会触发更多便签加载
                        try:
                            for _ in range(3):
                                self.driver.execute_script("window.scrollBy(0, 100);")
                                time.sleep(0.5)
                        except:
                            pass
                         
                        # 重新获取便签元素
                        current_elements = []
                        for by, selector in base_selectors:
                            try:
                                current_elements = self.driver.find_elements(by, selector)
                                if current_elements and len(current_elements) > last_count:
                                    break
                            except:
                                continue
                         
                        # 如果找到更多便签，更新列表
                        if current_elements and len(current_elements) > last_count:
                            last_count = len(current_elements)
                            note_elements = current_elements
                            logger.info(f"滚动后找到 {len(note_elements)} 个便签元素")
                            print(f"滚动后找到 {len(note_elements)} 个便签元素")
                            scroll_attempts = 0  # 重置尝试次数
                            strategy_index = 0  # 重置策略索引
                        else:
                            scroll_attempts += 1
                            strategy_index += 1
                            logger.info(f"第 {scroll_attempts} 次滚动未加载到新便签")
                             
                            # 额外尝试：直接寻找并滚动到便签列表容器
                            if scroll_attempts % 3 == 0:
                                try:
                                    logger.info("尝试直接滚动到便签列表容器...")
                                    # 尝试多种便签列表容器的选择器
                                    container_selectors = [
                                        "//div[contains(@class, 'note-list')]",
                                        "//div[contains(@class, 'notes-list')]",
                                        "//div[contains(@class, 'list-items')]"
                                    ]
                                    for container_selector in container_selectors:
                                        try:
                                            container = self.driver.find_element(By.XPATH, container_selector)
                                            # 滚动到容器底部
                                            self.driver.execute_script("arguments[0].scrollTop = arguments[0].scrollHeight;", container)
                                            time.sleep(scroll_pause_time)
                                            break
                                        except:
                                            continue
                                except:
                                    logger.debug("直接滚动到便签列表容器失败")
            
            # 添加元素信息调试和容器过滤
            filtered_note_elements = []
            if note_elements:
                logger.info(f"找到的便签元素详情:")
                for idx, el in enumerate(note_elements, 1):
                    try:
                        size = el.size
                        location = el.location
                        class_name = el.get_attribute('class')
                        
                        # 只显示前3个元素的详细信息
                        if idx <= 3:
                            logger.info(f"  元素{idx}: class={class_name}, size={size}, location={location}")
                        
                        # 过滤容器元素：
                        # 1. 排除高度特别大的元素（通常是容器）
                        # 2. 排除可能是容器的类名
                        is_container = False
                        if size['height'] > 300 and size['width'] < 500:
                            is_container = True
                            logger.info(f"  元素{idx}: 已识别为容器并过滤掉")
                        elif 'container' in class_name or 'wrapper' in class_name or 'list-items' in class_name:
                            is_container = True
                            logger.info(f"  元素{idx}: 已识别为容器并过滤掉")
                        
                        if not is_container:
                            filtered_note_elements.append(el)
                    except:
                        logger.info(f"  元素{idx}: 无法获取详细信息，跳过")
                        
            # 使用过滤后的便签元素列表
            note_elements = filtered_note_elements
            if note_elements:
                logger.info(f"过滤后找到 {len(note_elements)} 条有效便签")
                print(f"✅ 过滤后找到 {len(note_elements)} 条有效便签")
            else:
                logger.warning("过滤后未找到任何有效便签")
            
            # 如果还是没有找到，手动检查
            if not note_elements:
                logger.error("未能自动找到便签列表，请手动确认便签列表是否可见")
                print("❌ 未能自动找到便签列表")
                print("请在浏览器中确认便签列表是否可见，并检查页面结构")
                input("确认后按Enter键继续...")
                return notes_data
            
            logger.info(f"找到 {len(note_elements)} 条便签")
            print(f"✅ 找到 {len(note_elements)} 条便签")
            
            # 提取每条便签的内容
            for i, note_element in enumerate(note_elements, 1):
                # 更新当前处理的便签索引
                self.current_note_index = i
                 
                try:
                    # 记录开始处理时间
                    start_time = time.time()
                     
                    # 添加额外的延迟，避免操作过快
                    time.sleep(1)
                     
                    # 在处理每个便签前，确保页面已完全加载
                    try:
                        wait.until(EC.presence_of_all_elements_located((By.XPATH, "//*")))
                    except:
                        pass
                     
                    # 重新定位当前便签元素，避免元素过时
                    updated_note_element = note_element
                    try:
                        # 重新获取整个便签列表
                        updated_note_elements = []
                        for by, selector in base_selectors:
                            try:
                                updated_note_elements = self.driver.find_elements(by, selector)
                                if updated_note_elements and len(updated_note_elements) > 0:
                                    break
                            except:
                                continue
                         
                        # 如果成功重新获取了便签列表，并且索引有效，则使用更新后的元素
                        if updated_note_elements and (i-1) < len(updated_note_elements):
                            updated_note_element = updated_note_elements[i-1]
                            logger.info(f"已更新便签 {i} 元素引用")
                    except:
                        logger.debug(f"无法更新便签 {i} 元素引用，继续使用现有元素")
                     
                    # 尝试多种方式点击便签
                    clicked = False
                    original_url = self.driver.current_url
                    
                    # 添加点击前调试信息
                    try:
                        logger.info(f"尝试点击便签 {i}, 当前URL: {original_url}")
                    except:
                        pass
                    
                    # 尝试多种点击策略
                    click_methods = [
                        ('常规点击', lambda: updated_note_element.click()),
                        ('JavaScript点击', lambda: self.driver.execute_script("arguments[0].click();", updated_note_element)),
                        ('JavaScript直接执行点击事件', lambda: self.driver.execute_script("arguments[0].dispatchEvent(new MouseEvent('click', {bubbles: true, cancelable: true}));", updated_note_element))
                    ]
                     
                    # 如果有位置信息，添加坐标点击策略
                    try:
                        size = updated_note_element.size
                        # 导入ActionChains用于坐标点击
                        from selenium.webdriver.common.action_chains import ActionChains
                        click_methods.extend([
                            ('坐标点击（中心）', lambda: ActionChains(self.driver).move_to_element_with_offset(updated_note_element, size['width']//2, size['height']//2).click().perform()),
                            ('坐标点击（左上角）', lambda: ActionChains(self.driver).move_to_element_with_offset(updated_note_element, 5, 5).click().perform()),
                            ('坐标点击（底部）', lambda: ActionChains(self.driver).move_to_element_with_offset(updated_note_element, size['width']//2, size['height']-5).click().perform())
                        ])
                    except Exception as e:
                        logger.warning(f"无法获取元素尺寸信息，跳过坐标点击策略: {str(e)}")
                     
                    # 尝试所有点击方法
                    for method_name, click_func in click_methods:
                        try:
                            # 先滚动到元素可见区域
                            self.driver.execute_script("arguments[0].scrollIntoView({behavior: 'smooth', block: 'center'});", updated_note_element)
                            time.sleep(1)
                            
                            click_func()
                            logger.info(f"{method_name} 便签 {i} 成功")
                            clicked = True
                            break
                        except Exception as e:
                            logger.warning(f"{method_name} 便签 {i} 失败: {str(e)}")
                            # 每次失败后短暂暂停
                            time.sleep(0.5)
                     
                    # 如果所有点击方法都失败，记录详细错误并处理
                    if not clicked:
                        logger.error(f"无法点击便签 {i}")
                        self.consecutive_failures += 1
                        if self.consecutive_failures >= self.max_consecutive_failures:
                            logger.warning(f"连续失败次数达到上限，暂停处理")
                            time.sleep(5)
                            self.consecutive_failures = 0
                        continue
                    
                    if not clicked:
                        logger.error(f"无法点击便签 {i}")
                        self.consecutive_failures += 1
                        if self.consecutive_failures >= self.max_consecutive_failures:
                            logger.warning(f"连续失败次数达到上限，暂停处理")
                            time.sleep(5)
                            self.consecutive_failures = 0
                        continue
                    
                    # 重置连续失败计数
                    self.consecutive_failures = 0
                    
                    # 等待便签内容加载
                    time.sleep(3)  # 增加等待时间
                    
                    # 提取标题和内容
                    title = "无标题"
                    content = ""
                    title_element = None  # 确保初始化title_element变量
                    
                    # 尝试多种方式获取标题
                    title_selectors = [
                        (By.CLASS_NAME, "note-editor-title"),
                        (By.XPATH, "//div[contains(@class, 'title') and @contenteditable]"),
                        (By.XPATH, "//input[contains(@class, 'title')]"),
                        (By.XPATH, "//textarea[contains(@class, 'title')]"),
                        (By.CSS_SELECTOR, "[class*='note'][class*='editor'][class*='title']"),
                        (By.XPATH, "//*[contains(text(), '标题')]/following-sibling::*[1]"),
                        (By.TAG_NAME, "h1"),
                        (By.TAG_NAME, "h2"),
                        (By.TAG_NAME, "h3")
                    ]
                    
                    # 加强版标题提取，确保title_element始终有定义
                    for by, selector in title_selectors:
                        try:
                            temp_title_element = self.driver.find_element(by, selector)
                            if temp_title_element:
                                title_element = temp_title_element  # 更新title_element变量
                                if title_element.text.strip():
                                    title = title_element.text.strip()
                                    break
                                elif hasattr(title_element, 'get_attribute'):
                                    # 尝试获取输入框的值
                                    value = title_element.get_attribute('value')
                                    if value and value.strip():
                                        title = value.strip()
                                        break
                        except (NoSuchElementException, WebDriverException):
                            continue
                    
                    # 尝试多种方式获取内容 - 增强版
                    content_selectors = [
                        # 主要内容选择器
                        (By.CLASS_NAME, "note-editor-content"),
                        (By.XPATH, "//div[contains(@class, 'content') and @contenteditable]"),
                        (By.XPATH, "//div[contains(@class, 'note-content')]"),
                        (By.XPATH, "//div[contains(@class, 'editor-content')]"),
                        (By.XPATH, "//div[contains(@class, 'note-editor')]//div[contains(@class, 'content')]"),
                        # 输入框类型内容
                        (By.XPATH, "//input[contains(@class, 'content')]"),
                        (By.XPATH, "//textarea[contains(@class, 'content')]"),
                        # 通用选择器
                        (By.CSS_SELECTOR, "[class*='note'][class*='editor'][class*='content']"),
                        (By.XPATH, "//*[contains(text(), '内容')]/following-sibling::*[1]"),
                        # 标签类型
                        (By.TAG_NAME, "p"),
                        (By.TAG_NAME, "div"),
                        (By.TAG_NAME, "span"),
                        # 可能的容器元素
                        (By.XPATH, "//div[@role='textbox']"),
                        (By.XPATH, "//div[@contenteditable='true']"),
                        (By.XPATH, "//div[contains(@class, 'document')]"),
                        (By.XPATH, "//div[contains(@class, 'body')]"),
                        (By.XPATH, "//div[contains(@class, 'main')]")
                    ]
                    
                    # 添加调试信息
                    logger.info(f"开始提取内容，尝试 {len(content_selectors)} 种选择器")
                    content_found = False
                    
                    for by, selector in content_selectors:
                        try:
                            content_element = self.driver.find_element(by, selector)
                            # 放宽条件：只要元素存在且有有效内容（包括不为空的value属性）
                            if content_element:
                                # 尝试获取文本内容
                                if content_element.text and content_element.text.strip():
                                    # 避免与标题重复，但如果是唯一的内容来源则接受
                                    if title_element is None or content_element != title_element or content == "":
                                        temp_content = content_element.text.strip()
                                        # 只在找到有效内容时更新content
                                        if temp_content and (not content or len(temp_content) > len(content)):
                                            content = temp_content
                                            content_found = True
                                            logger.info(f"找到内容（通过文本）: {temp_content[:30]}...")
                                        # 不立即break，继续寻找可能更完整的内容
                                
                                # 尝试获取value属性（适用于输入框类元素）
                                if hasattr(content_element, 'get_attribute'):
                                    value = content_element.get_attribute('value')
                                    if value and value.strip():
                                        # 避免与标题重复
                                        if title_element is None or content_element != title_element or content == "":
                                            temp_content = value.strip()
                                            if temp_content and (not content or len(temp_content) > len(content)):
                                                content = temp_content
                                                content_found = True
                                                logger.info(f"找到内容（通过value属性）: {temp_content[:30]}...")
                        except (NoSuchElementException, WebDriverException, AttributeError) as e:
                            logger.debug(f"选择器 {by}:{selector} 查找失败: {str(e)}")
                            continue
                    
                    # 如果内容为空，尝试获取整个页面文本中的主要内容
                    if not content or len(content) < 10:  # 放宽条件，只要内容太短就尝试备用方案
                        try:
                            page_text = self.driver.find_element(By.TAG_NAME, "body").text
                            if page_text and page_text.strip():
                                logger.info(f"尝试通过页面文本提取内容，总长度: {len(page_text)}字符")
                                # 优化过滤条件，减少过度过滤
                                filtered_text = "\n".join([line for line in page_text.split("\n") 
                                                          if line.strip() and 
                                                          not ("登录" in line and len(line.strip()) < 20) and 
                                                          not ("注册" in line and len(line.strip()) < 20) 
                                                          and not ("导航" in line and len(line.strip()) < 20) 
                                                          and not ("菜单" in line and len(line.strip()) < 20)])
                            
                                # 如果过滤后文本不为空，且比当前内容长，则使用
                                if filtered_text and filtered_text.strip() and (not content or len(filtered_text) > len(content)):
                                    content = filtered_text.strip()[:2000]  # 增加限制长度
                                    logger.info(f"通过页面文本提取到内容: {content[:30]}...")
                        except Exception as e:
                            logger.error(f"获取页面文本失败: {str(e)}")
                            pass
                    
                    # 添加最终调试信息
                    logger.info(f"内容提取完成，是否找到内容: {content_found}, 内容长度: {len(content)}字符")
                    
                    # 添加到数据列表
                    note_data = {
                        "id": i,
                        "title": title,
                        "content": content,
                        "export_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                    }
                    notes_data.append(note_data)
                    
                    # 显示进度
                    logger.info(f"已提取便签 {i}/{len(note_elements)}: {title[:20]}...")
                    print(f"   已提取便签 {i}/{len(note_elements)}: {title[:20]}...")
                    
                    # 强制返回到便签列表，优先使用直接网址访问（更稳定）
                    logger.info(f"尝试返回到便签列表，准备提取下一个便签")
                    
                    # 优化：优先使用直接网址访问，这是最稳定的方法
                    try:
                        logger.info("优先使用直接网址访问返回便签列表")
                        self.driver.get(self.note_list_url)
                        time.sleep(3)  # 给足够时间加载页面
                        # 验证是否成功返回
                        current_url = self.driver.current_url
                        if "note" in current_url and "detail" not in current_url and "edit" not in current_url:
                            logger.info(f"成功返回到便签列表，当前URL: {current_url}")
                            # 重新获取便签列表元素，使用已定义的base_selectors
                            for by, selector in base_selectors:
                                try:
                                    temp_elements = wait.until(EC.presence_of_all_elements_located((by, selector)))
                                    if temp_elements and len(temp_elements) > 0:
                                        note_elements = temp_elements
                                        break
                                except:
                                    continue
                            continue  # 成功返回，继续下一个便签
                    except Exception as e:
                        logger.warning(f"直接网址访问失败: {str(e)}")
                        
                    # 备选方法1: 尝试点击返回按钮（仅在直接访问失败时使用）
                    back_button_found = False
                    try:
                        # 使用多种返回按钮选择器
                        back_selectors = [
                            "//button[contains(@class, 'back') or contains(text(), '返回') or contains(@class, 'icon-arrow-left') or contains(@class, 'btn-back')]",
                            "//i[contains(@class, 'icon-arrow-left')]",
                            "//span[contains(text(), '返回')]",
                            "//a[contains(@class, 'back') or contains(text(), '返回')]",
                            "//div[contains(@class, 'back') or contains(text(), '返回')]",
                            "//*[@id='back-button' or @id='btn-back']"
                        ]
                        
                        for selector in back_selectors:
                            try:
                                back_button = self.driver.find_element(By.XPATH, selector)
                                if back_button.is_displayed() or back_button.size['width'] > 0:
                                    back_button.click()
                                    back_button_found = True
                                    time.sleep(2)
                                    break
                            except:
                                continue
                    except:
                        pass
                    
                    # 备选方法2: 如果没有找到返回按钮，使用浏览器的后退功能
                    if not back_button_found:
                        try:
                            logger.warning("未找到返回按钮，尝试使用浏览器后退功能")
                            self.driver.back()
                            time.sleep(2)
                        except:
                            pass
                    
                    # 再次尝试强制使用直接网址访问
                    try:
                        # 检查当前页面是否已返回到便签列表
                        current_url = self.driver.current_url
                        if "note" not in current_url or "detail" in current_url or "edit" in current_url:
                            logger.warning("返回仍未成功，再次尝试直接访问便签列表页面")
                            self.driver.get(self.note_list_url)
                            time.sleep(3)
                            # 重新获取便签列表元素，使用已定义的base_selectors
                            for by, selector in base_selectors:
                                try:
                                    temp_elements = wait.until(EC.presence_of_all_elements_located((by, selector)))
                                    if temp_elements and len(temp_elements) > 0:
                                        note_elements = temp_elements
                                        break
                                except:
                                    continue
                    except:
                        pass
                    
                    # 防止处理时间过长，但只跳过当前便签而不是整个循环
                    if time.time() - start_time > self.single_note_timeout:
                        logger.warning(f"处理便签 {i} 时间过长，跳过当前便签")
                        # 尝试强制返回便签列表
                        try:
                            self.driver.get(self.note_list_url)
                            time.sleep(2)
                        except:
                            pass
                        continue
                except Exception as e:
                    logger.error(f"提取便签 {i} 时出错: {str(e)}")
                    print(f"⚠️ 提取便签 {i} 时出错: {str(e)}")
                    self.consecutive_failures += 1
                    # 尝试返回便签列表
                    try:
                        self.driver.get(self.note_list_url)
                        time.sleep(3)
                    except:
                        pass
                    if self.consecutive_failures >= self.max_consecutive_failures:
                        logger.warning(f"连续失败次数达到上限，暂停处理")
                        time.sleep(5)
                        self.consecutive_failures = 0
                    continue
            
        except Exception as e:
            logger.error(f"获取便签列表失败: {str(e)}")
            print(f"❌ 获取便签列表失败: {str(e)}")
            print("请确认您已成功登录并能看到便签列表")
        
        # 去重处理，防止重复的便签内容
        unique_notes = []
        seen_contents = set()
        for note in notes_data:
            # 使用内容的前50个字符作为去重的依据
            content_key = note['content'][:50] if note['content'] else ""
            if content_key not in seen_contents:
                seen_contents.add(content_key)
                unique_notes.append(note)
        
        if len(unique_notes) < len(notes_data):
            logger.info(f"已移除 {len(notes_data) - len(unique_notes)} 条重复的便签")
            print(f"⚠️ 已移除 {len(notes_data) - len(unique_notes)} 条重复的便签")
        
        return unique_notes
    
    def export_notes(self, notes_data):
        """导出便签数据"""
        if not notes_data:
            logger.warning("没有可导出的便签数据")
            print("⚠️ 没有可导出的便签数据")
            return False
        
        try:
            # 生成时间戳和文件名
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            file_path = os.path.join(self.output_dir, f"mi_notes_auto_{timestamp}.json")
            
            # 保存为JSON文件
            with open(file_path, "w", encoding="utf-8") as f:
                json.dump(notes_data, f, ensure_ascii=False, indent=2)
            
            # 同时保存为TXT文件，方便查看
            txt_file_path = os.path.join(self.output_dir, f"mi_notes_auto_{timestamp}.txt")
            with open(txt_file_path, "w", encoding="utf-8") as f:
                for note in notes_data:
                    f.write(f"===== 便签 {note['id']}: {note['title']} =====\n")
                    f.write(f"{note['content']}\n")
                    f.write(f"\n导出时间: {note['export_time']}\n")
                    f.write("\n" + "="*50 + "\n\n")
            
            logger.info(f"便签已成功导出到JSON: {file_path}")
            logger.info(f"便签已成功导出到TXT: {txt_file_path}")
            print(f"\n✅ 导出完成！")
            print(f"已将 {len(notes_data)} 条便签导出到:")
            print(f"   JSON格式: {file_path}")
            print(f"   TXT格式: {txt_file_path}")
            return True
        except Exception as e:
            logger.error(f"导出便签失败: {str(e)}")
            print(f"❌ 导出便签失败: {str(e)}")
            return False
    
    def close_browser(self):
        """关闭浏览器"""
        if self.driver:
            try:
                self.driver.quit()
                logger.info("浏览器已关闭")
            except Exception as e:
                logger.error(f"关闭浏览器时出错: {str(e)}")
    
    def run(self):
        """运行导出流程"""
        success = False
        retry_count = 0
        
        while retry_count < self.max_retries:
            try:
                # 检查环境
                if not self.check_environment():
                    return False
                
                # 初始化浏览器
                if not self.init_browser():
                    retry_count += 1
                    if retry_count >= self.max_retries:
                        return False
                    logger.warning(f"浏览器初始化失败，{self.retry_interval}秒后重试 ({retry_count}/{self.max_retries})")
                    time.sleep(self.retry_interval)
                    continue
                
                # 导航到便签页面
                if not self.navigate_to_notes():
                    retry_count += 1
                    if retry_count >= self.max_retries:
                        return False
                    logger.warning(f"导航失败，{self.retry_interval}秒后重试 ({retry_count}/{self.max_retries})")
                    time.sleep(self.retry_interval)
                    continue
                
                # 获取所有便签
                notes_data = self.get_all_notes()
                
                # 导出便签
                if notes_data:
                    success = self.export_notes(notes_data)
                else:
                    logger.warning("没有找到任何便签")
                    print("⚠️ 没有找到任何便签")
                    # 提供备选方案
                    print("\n如果您确认浏览器中有便签但无法自动提取，可以尝试手动导出助手:")
                    print("   python mi_note_manual_export_assistant.py")
                
                break  # 成功运行后跳出循环
                
            except WebDriverException as e:
                retry_count += 1
                logger.error(f"导出过程中发生WebDriver错误: {str(e)}")
                print(f"❌ 导出过程中发生WebDriver错误: {str(e)}")
                if retry_count < self.max_retries:
                    logger.warning(f"{self.retry_interval}秒后重试 ({retry_count}/{self.max_retries})")
                    time.sleep(self.retry_interval)
                else:
                    print("❌ 达到最大重试次数，导出失败")
            except Exception as e:
                logger.error(f"导出过程中发生错误: {str(e)}")
                print(f"❌ 导出过程中发生错误: {str(e)}")
                break
            finally:
                # 根据设置决定是否关闭浏览器
                if not self.keep_browser_open:
                    # 关闭浏览器
                    self.close_browser()
                else:
                    logger.info("浏览器保持打开状态")
                    print("\n✅ 浏览器保持打开状态，您可以手动查看或操作")
        
        return success


def main():
    print("="*50)
    print("优化版小米便签自动化导出工具")
    print("="*50)
    print("这个工具将尝试完全自动化导出小米便签内容")
    print("工具会引导您完成登录并自动提取便签")
    print("="*50)
    
    # 询问用户是否需要保持浏览器不关闭
    keep_browser_open = input("是否保持浏览器不关闭？(y/n，默认n): ").lower() == 'y'
    
    exporter = MiNoteAutoExporter(keep_browser_open=keep_browser_open)
    success = exporter.run()
    
    print("\n" + "="*50)
    print("导出流程已结束")
    if success:
        print("✅ 导出成功")
    else:
        print("❌ 导出失败，请查看日志文件获取详细信息")
        print("\n建议尝试：")
        print("1. 确认ChromeDriver版本与Chrome浏览器版本匹配")
        print("2. 尝试手动导出助手: python mi_note_manual_export_assistant.py")
        print("3. 检查Chrome浏览器是否正常显示便签列表")
    print("="*50)
    
    input("按Enter键退出...")


if __name__ == "__main__":
    main()